<!doctype html>
<html lang="en">
  <body>
    <script src="chrome://__addonRef__/content/lib/js/go.js"></script>
    <style>
      html,
      body,
      div {
        height: 100%;
        width: 100%;
        padding: 0;
        margin: 0;
      }
      #canvas {
        position: absolute;
        height: 100%;
        width: 100%;
      }
    </style>
    <div id="allSampleContent" class="p-4 w-full">
      <script id="code">
        // This variation on ForceDirectedLayout does not move any selected Nodes
        // but does move all other nodes (vertexes).
        class ContinuousForceDirectedLayout extends go.ForceDirectedLayout {
          isFixed(v) {
            return v.node.isSelected;
          }

          // optimization: reuse the ForceDirectedNetwork rather than re-create it each time
          doLayout(coll) {
            if (!this._isObserving) {
              this._isObserving = true;
              // cacheing the network means we need to recreate it if nodes or links have been added or removed or relinked,
              // so we need to track structural model changes to discard the saved network.
              this.diagram.addModelChangedListener((e) => {
                // modelChanges include a few cases that we don't actually care about, such as
                // "nodeCategory" or "linkToPortId", but we'll go ahead and recreate the network anyway.
                // Also clear the network when replacing the model.
                if (
                  e.modelChange !== "" ||
                  (e.change === go.ChangedEvent.Transaction &&
                    e.propertyName === "StartingFirstTransaction")
                ) {
                  this.network = null;
                }
              });
            }
            var net = this.network;
            if (net === null) {
              // the first time, just create the network as normal
              this.network = net = this.makeNetwork(coll);
            } else {
              // but on reuse we need to update the LayoutVertex.bounds for selected nodes
              this.diagram.nodes.each((n) => {
                var v = net.findVertex(n);
                if (v !== null) v.bounds = n.actualBounds;
              });
            }
            // now perform the normal layout
            super.doLayout(coll);
            // doLayout normally discards the LayoutNetwork by setting Layout.network to null;
            // here we remember it for next time
            this.network = net;
          }
        }
        // end ContinuousForceDirectedLayout
        var Diagram;

        function init() {
          window.addEventListener("message", handler, false);

          // Since 2.2 you can also author concise templates with method chaining instead of GraphObject.make
          // For details, see https://gojs.net/latest/intro/buildingObjects.html
          const $ = go.GraphObject.make; // for conciseness in defining templates

          Diagram = $(
            go.Diagram,
            "DiagramDiv", // must name or refer to the DIV HTML element
            {
              initialAutoScale: go.Diagram.UniformToFill, // an initial automatic zoom-to-fit
              contentAlignment: go.Spot.Center, // align document to the center of the viewport
              layout: $(
                ContinuousForceDirectedLayout, // automatically spread nodes apart while dragging
                { defaultSpringLength: 15, defaultElectricalCharge: 30 },
              ),
              // do an extra layout at the end of a move
              SelectionMoved: (e) => e.diagram.layout.invalidateLayout(),
            },
          );

          // dragging a node invalidates the Diagram.layout, causing a layout during the drag
          Diagram.toolManager.draggingTool.doMouseMove = function () {
            go.DraggingTool.prototype.doMouseMove.call(this);
            if (this.isActive) {
              this.diagram.layout.invalidateLayout();
            }
          };

          // define each Node's appearance
          Diagram.nodeTemplate = $(
            go.Node,
            "Vertical", // the whole node panel
            // define the node's outer shape, which will surround the TextBlock
            $(
              go.Panel,
              "Spot",
              $(
                go.Shape,
                "Circle",
                {
                  name: "SHAPE",
                  fill: "lightgray", // default value, but also data-bound
                  strokeWidth: 0,
                  desiredSize: new go.Size(10, 10),
                },
                new go.Binding("fill", "isSelected", (s, obj) =>
                  s ? "#f2ac46" : "lightgray",
                ).ofObject(),
              ),
            ),
            $(
              go.TextBlock,
              {
                textAlign: "center",
                maxSize: new go.Size(100, NaN),
                editable: true,
                textEdited: editNode,
              },
              new go.Binding("text", "text").makeTwoWay(),
              new go.Binding("stroke", "stroke"),
            ),
          );
          // the rest of this app is the same as samples/conceptMap.html

          // selected nodes show a button for adding children
          Diagram.nodeTemplate.selectionAdornmentTemplate = $(
            go.Adornment,
            "Spot",
            $(
              go.Panel,
              "Auto",
              // this Adornment has a rectangular blue Shape around the selected node
              $(go.Shape, { fill: null, stroke: "dodgerblue", strokeWidth: 3 }),
              $(go.Placeholder, { margin: new go.Margin(4, 4, 0, 4) }),
            ),
            // and this Adornment has a Button to the right of the selected node
            $(
              "Button",
              {
                alignment: go.Spot.Right,
                alignmentFocus: go.Spot.Left,
                click: jumpNode, // define click behavior for this Button in the Adornment
              },
              $(
                go.TextBlock,
                "↗️", // the Button content
                { font: "bold 8pt sans-serif" },
              ),
            ),
          );

          // replace the default Link template in the linkTemplateMap
          Diagram.linkTemplate = $(
            go.Link, // the whole link panel
            $(
              go.Shape, // the link shape
              { stroke: "grey" },
            ),
            $(
              go.Shape, // the arrowhead
              { toArrow: "standard", stroke: null },
            ),
            $(go.Panel, "Auto"),
          );
          window.postMessage({ type: "ready" }, "*");
          getData();
        }

        function getData() {
          window.postMessage({ type: "getMindMapData" }, "*");
        }

        function setData(nodes) {
          const nodeDataArray = [{ key: 999, text: "📄", parent: undefined }];
          const linkDataArray = [];
          const wrapText = (text) => {
            let wrappedText = "";
            for (let i = 0; i < text.length / 50; i++) {
              wrappedText += text.slice(50 * i, 50 * i + 50);
              wrappedText += "\n";
            }
            return wrappedText;
          };
          for (const node of nodes) {
            const parent =
              node.parent.model.id === -1 ? 999 : node.parent.model.id;
            nodeDataArray.push({
              key: node.model.id,
              text: `${node.model.level === 7 ? "🔗" : ""}${wrapText(
                node.model.name,
              )}`,
              stroke: "-moz-DialogText",
              lineIndex: node.model.lineIndex,
              level: node.model.level,
              noteLink: node.model.level === 7 ? node.model.link : "",
            });
            linkDataArray.push({
              from: parent,
              to: node.model.id,
              text: "",
            });
          }
          console.log(nodeDataArray, linkDataArray);
          Diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
        }

        function jumpNode(e, obj) {
          var adorn = obj.part;
          var oldnode = adorn.adornedPart;
          var olddata = oldnode.data;
          if (olddata.noteLink) {
            window.postMessage(
              { type: "openNote", link: olddata.noteLink, id: olddata.key },
              "*",
            );
          } else {
            window.postMessage(
              {
                type: "jumpNode",
                lineIndex: olddata.lineIndex,
                id: olddata.key,
              },
              "*",
            );
          }
        }

        function editNode(textBlock, previousText, currentText) {
          const adorn = textBlock.part;
          const data = adorn.data;
          if (data.level === 7) {
            alert("Link cannot be edited in mind map");
            return false;
          }
          window.postMessage(
            {
              type: "editNode",
              lineIndex: data.lineIndex,
              text: data.text,
            },
            "*",
          );
        }

        function handler(e) {
          console.log(e);
          switch (e.data.type) {
            case "setMindMapData":
              setData(e.data.nodes);
              break;
            case "saveImage":
              const imgString = Diagram.makeImageData({
                scale: 1,
              });
              window.postMessage(
                {
                  type: "saveImageReturn",
                  image: imgString,
                },
                "*",
              );
              break;
            case "saveSVG":
              const svgElement = Diagram.makeSvg({
                scale: 1,
              });
              window.postMessage(
                {
                  type: "saveSVGReturn",
                  image: svgElement.outerHTML,
                },
                "*",
              );
              break;
            default:
              break;
          }
        }

        window.addEventListener("DOMContentLoaded", init);

        var resizeTime = new Date().getTime();
        window.addEventListener("resize", (e) => {
          const canvas = document.getElementsByTagName("canvas")[0];
          const div = document.getElementById("DiagramDiv");
          canvas.setAttribute("height", div.getAttribute("height"));
          canvas.setAttribute("width", div.getAttribute("width"));
          const currentResize = new Date().getTime();
          resizeTime = currentResize;
          // Delay update
          setTimeout(() => {
            if (resizeTime === currentResize) {
              getData();
            }
          }, 500);
        });
      </script>

      <div id="sample">
        <div
          id="DiagramDiv"
          style="
            width: 100%;
            position: relative;
            -webkit-tap-highlight-color: rgba(255, 255, 255, 0);
            cursor: auto;
            font: 13px sans-serif;
            overflow: hidden;
          "
        >
          <canvas
            tabindex="0"
            style="
              position: absolute;
              top: 0px;
              left: 0px;
              z-index: 2;
              user-select: none;
              touch-action: none;
              cursor: auto;
            "
            >This text is displayed if your browser does not support the Canvas
            HTML element.</canvas
          >
        </div>
      </div>
    </div>
  </body>
</html>
